{
  This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
  the Free Software Foundation; version 2 of the License.
   
  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.
}

// Copyright (c) 2010 2011 2012 - J. Aldo G. de Freitas Junior

{$mode objfpc}
{$H+}

Unit
	Commands;

Interface

Uses
	Classes,
	BaseException,
	SysUtils,
	StrUtils,
	Stacks,
	Tree,
	XMLNodes,
	XMLLoader,
	TabularData,
	ExprNodes,
	ExprLoader,
	XMLTemplate,
	Nodes,
	Process,
	MD5;

Const
	ccConfigFile = 'config.xml';

Type
	TCommandParser = Class(TExprRootNode)
	Public
		Constructor Create(Const aOwner : TTreeNode); Override;
	End;

	TPrintCommand = Class(TCommandWithoutParameters)
	Public
		Class Function RegisteredName : String; Override;
		Procedure Evaluate; Override;
	End;
	
	TListCommand = Class(TCommandWithoutParameters)
	Public
		Class Function RegisteredName : String; Override;
		Procedure Evaluate; Override;
	End;
	
	TAddCommand = Class(TCommandWithClassNameAndProperties)
	Public
		Class Function RegisteredName : String; Override;
		Procedure Evaluate; Override;
	End;
	
	TParseTemplateCommand = Class(TCommandWithExpressionList)
	Public
		Class Function RegisteredName : String; Override;
		Procedure Evaluate; Override;
	End;
	
	TSaveCommand = Class(TCommandWithoutParameters)
	Public
		Class Function RegisteredName : String; Override;
		Procedure Evaluate; Override;
	End;
	
	TUpCommand = Class(TCommandWithoutParameters)
	Public
		Class Function RegisteredName : String; Override;
		Procedure Evaluate; Override;	
	End;
	
	TDeleteCommand = Class(TCommandWithoutParameters)
	Public
		Class Function RegisteredName : String; Override;
		Procedure Evaluate; Override;	
	End;
	
	TSetCommand = Class(TCommandWithProperties)
	Public
		Class Function RegisteredName : String; Override;
		Procedure Evaluate; Override;	
	End;
	
	TPutCommand = Class(TCommandWithExpressionList)
	Public
		Class Function RegisteredName : String; Override;
		Procedure Evaluate; Override;	
	End;

	TExecuteCommand = Class(TCommandWithProperties)
	Public
		Class Function RegisteredName : String; Override;
		Procedure Evaluate; Override;	
	End;

	TEditCommand = Class(TCommandWithoutParameters)
	Public
		Class Function RegisteredName : String; Override;
		Procedure Evaluate; Override;	
	End;
	
	TExportCommand = Class(TCommandWithIdentifier)
	Public
		Class Function RegisteredName : String; Override;
		Procedure Evaluate; Override;	
	End;
	
	TCommitCommand = Class(TCommandWithoutParameters)
	Public
		Class Function RegisteredName : String; Override;
		Procedure Evaluate; Override;	
	End;
	
	TUpdateCommand = Class(TCommandWithoutParameters)
	Public
		Class Function RegisteredName : String; Override;
		Procedure Evaluate; Override;	
	End;
	
	TLoadFromTextFile = Class(TCommandWithExpression)
	Public
		Class Function RegisteredName : String; Override;
		Procedure Evaluate; Override;
	End;
	
Implementation

Procedure DoParseTemplate(Const aStack : TVirtualMachineStack; Const aTemplateIn, aTemplateOut : String);
Var
	lTemplate : TXMLTemplateRootNode;
	lFileStream : TFileStream;
	lBuffer : String;
	lStringStream : TStringStream;
Begin
	lTemplate := Nil;
	lFileStream := Nil;
	lStringStream := Nil;
	lBuffer := '';
	Try
		lTemplate := TXMLTemplateRootNode.Create(Nil);
		If FileExists(aTemplateOut) Then
			DeleteFile(aTemplateOut);
		lFileStream := TFileStream.Create(aTemplateOut, fmCreate);
		lTemplate.LoadFromFile(aTemplateIn);
		lTemplate.PropagateStack(aStack);
		lBuffer := lTemplate.Emit;
		lBuffer := StringReplace(lBuffer, ccNewLine, #13, [ rfReplaceAll, rfIgnoreCase ]);
		lBuffer := StringReplace(lBuffer, ccSpace, ' ', [ rfReplaceAll, rfIgnoreCase ]);
		lBuffer := StringReplace(lBuffer, ccTab, #09, [ rfReplaceAll, rfIgnoreCase ]);
		lBuffer := AdjustLineBreaks(lBuffer);
		lBuffer := DelSpace1(lBuffer);
		lStringStream := TStringStream.Create(lBuffer);
		lStringStream.Seek(0, soFromBeginning);
		lFileStream.CopyFrom(lStringStream, lStringStream.Size);
	Finally
		FreeAndNil(lTemplate);
		FreeAndNil(lFileStream);
		FreeAndNil(lStringStream);
	End;
End;

Procedure DoRestartService(Const aStack : TVirtualMachineStack; Const aService : String);
Var
	lSave : TXMLNode;
	lEntry : TServiceEntryNode;
Begin
	Try
		lSave := aStack.Focused;
		aStack.Focused := aStack.Focused.FindRoot.Find('services') As TXMLNode;
		aStack.Focused.First;
		While Not aStack.Focused.IsAfterLast Do
		Begin
			lEntry := aStack.Focused.GetCurrent As TServiceEntryNode;
			If lEntry.Name = aService Then
				ExecuteProcess('/bin/bash', ['-c', lEntry.OnRestart]);				
			aStack.Focused.Next;
		End;
	Finally
		aStack.Focused := lSave;
	End;
End;

// TCommandParser

Constructor TCommandParser.Create(Const aOwner : TTreeNode);
Begin
	Inherited Create(aOwner);
	ClassFactory.Register(TPrintCommand);
	ClassFactory.Register(TListCommand);
	ClassFactory.Register(TParseTemplateCommand);
	ClassFactory.Register(TAddCommand);
	ClassFactory.Register(TSaveCommand);
	ClassFactory.Register(TUpCommand);
	ClassFactory.Register(TDeleteCommand);
	ClassFactory.Register(TSetCommand);
	ClassFactory.Register(TPutCommand);
	ClassFactory.Register(TExecuteCommand);
	ClassFactory.Register(TEditCommand);
	ClassFactory.Register(TExportCommand);
	ClassFactory.Register(TCommitCommand);
	ClassFactory.Register(TUpdateCommand);
	ClassFactory.Register(TLoadFromTextFile);
End;

// TPrintCommand

Class Function TPrintCommand.RegisteredName : String;
Begin
	Result := 'print';
End;

Procedure TPrintCommand.Evaluate;
Var
	lTabularData : TTabularData;
	lX : Integer;
	lCurrent : TXMLNode;
Begin
	// Debug  WriteLn('TPrintCommand');
	If Stack.Focused.IsCapableOf('TPrintCommand') Then
	Begin
		lTabularData := TTabularData.Create;
		Try
			lTabularData.Clear;
			Stack.Focused.First;
			While Not Stack.Focused.IsAfterLast Do
			Begin
				lTabularData.NewLine;
				lCurrent := (Stack.Focused.GetCurrent As TXMLNode);
				lTabularData.SetData('#', IntToStr(Stack.Focused.GetCurrentIndex));
				lTabularData.SetData('Type', lCurrent.RegisteredName);
				For lX := 0 To lCurrent.PropertyCount - 1 Do
					lTabularData.SetData(lCurrent.GetPropertyName(lX), lCurrent.PropertiesByIndex[lX]);
				Stack.Focused.Next;
			End;
			Stack.OutLn('');
			If Length(lTabularData.Lines) > 0 Then
				Stack.Out(lTabularData.Print)
			Else
				Stack.OutLn('Empty set.');
			Stack.OutLn('');
		Finally
			FreeAndNil(lTabularData);
		End;
	End
	Else
		RaiseError('You cannot use that command here.');
End;

// TListCommand

Class Function TListCommand.RegisteredName : String;
Begin
	Result := 'list';
End;

Procedure TListCommand.Evaluate;
Begin
	// Debug  WriteLn('TListCommand');
	If Stack.Focused.IsCapableOf('TListCommand') Then
	Begin
		Stack.Focused.First;
		While Not Stack.Focused.IsAfterLast Do
		Begin
			Stack.OutLn((Stack.Focused.GetCurrent As TXMLNode).IndexedName);
			Stack.Focused.Next;
		End;
	End
	Else
		RaiseError('You cannot use that command here.');
End;

// TAddCommand

Class Function TAddCommand.RegisteredName : String;
Begin
	Result := 'add';
End;

Procedure TAddCommand.Evaluate;
Begin
	// Debug  WriteLn('TAddCommand');
	If Stack.Focused.IsCapableOf('TAddCommand') Then
	Begin
		If Stack.Focused.Factory.IsRegisteredClass(NameOfClass) Then
		Begin
			Stack.Focused := Stack.Focused.Factory.Build(NameOfClass, Stack.Focused) As TXMLNode;
			Stack.NameValue.Save;
			Try
				Try
					If Assigned(Properties) Then
						Properties.Evaluate;
					Stack.Focused.InitRTTIFromNameValuePairs(Stack.NameValue.Top);
				Finally
					Stack.NameValue.Restore;
				End;
			Except
			On E: Exception Do
				Raise EInterpreterException.Create(Row, Col, Source, E);
			End;
		End
		Else
			RaiseError('You cannot create a child of that class here.');
	End
	Else
		RaiseError('You cannot use that command here.');
End;

// TParseTemplateCommand

Class Function TParseTemplateCommand.RegisteredName : String;
Begin
	Result := 'template';
End;

Procedure TParseTemplateCommand.Evaluate;
Begin
	// Debug WriteLn('TParseTemplateCommand');
	If Stack.Focused.IsCapableOf('TParseTemplateCommand') Then
	Begin
		Try
			Try
				If Assigned(ExpressionList) Then
					ExpressionList.Evaluate
				Else
					RaiseError('You must provide parameters.');
				Stack.Enter(2);
				DoParseTemplate(Stack, Stack.Top.Peek(0), Stack.Top.Peek(1));
			Finally
				Stack.Leave(0);
			End;
		Except
			On E: Exception Do
				Raise EInterpreterException.Create(Row, Col, Source, E);
		End;
	End
	Else
		RaiseError('You cannot use that command here.');
End;

// TSaveCommand

Class Function TSaveCommand.RegisteredName : String;
Begin
	Result := 'save';
End;

Procedure TSaveCommand.Evaluate;
Var
	lIterator : TXMLAsTextIterator;
	lFileStream : TFileStream;
	lStringStream : TStringStream;
Begin
	// Debug WriteLn('TSaveCommand');
	If Stack.Focused.IsCapableOf('TSaveCommand') Then
	Begin
		lFileStream := Nil;
		lStringStream := Nil;
		lIterator := Nil;
		Try
			lIterator := TXMLAsTextIterator.Create;
			lIterator.Visit((Stack.Focused.FindRoot) As TXMLNode);
			If FileExists(ccConfigFile) Then
				DeleteFile(ccConfigFile);
			lFileStream := TFileStream.Create(ccConfigFile, fmCreate);
			lStringStream := TStringStream.Create(lIterator.Output);
			lStringStream.Seek(0, soFromBeginning);
			lFileStream.CopyFrom(lStringStream, lStringStream.Size);
		Finally
			FreeAndNil(lFileStream);
			FreeAndNil(lStringStream);
			FreeAndNil(lIterator);
		End;
	End
	Else
		RaiseError('You cannot use that command here.');
End;

// TUpCommand

Class Function TUpCommand.RegisteredName : String;
Begin
	Result := 'up';
End;

Procedure TUpCommand.Evaluate;
Begin
	// Debug WriteLn('TUpCommand');
	If Stack.Focused.IsCapableOf('TUpCommand') Then
		Stack.Focused := Stack.Focused.Owner As TXMLNode
	Else
		RaiseError('You cannot use that command here.');
End;

// TDeleteCommand

Class Function TDeleteCommand.RegisteredName : String;
Begin
	Result := 'delete';
End;

Procedure TDeleteCommand.Evaluate;
Var
	lNode : TXMLNode;
Begin
	// Debug WriteLn('TDeleteCommand');
	If Stack.Focused.IsCapableOf('TDeleteCommand') Then
		If Assigned(Stack.Focused.Owner) Then
		Begin
			lNode := Stack.Focused;
			Stack.Focused := Stack.Focused.Owner As TXMLNode;
			Stack.Focused.Delete(lNode);
		End
		Else
	Else
		RaiseError('You cannot use that command here.');
End;

// TSetCommand

Class Function TSetCommand.RegisteredName : String;
Begin
	Result := 'set';
End;

Procedure TSetCommand.Evaluate;
Begin
	// Debug WriteLn('TSetCommand');
	If Stack.Focused.IsCapableOf('TSetCommand') Then
	Begin
		Stack.NameValue.Save;
		Try
			Try
				If Assigned(Properties) Then
					Properties.Evaluate
				Else
					RaiseError('You must provide properties.');
				Stack.Focused.InitRTTIFromNameValuePairs(Stack.NameValue.Top);
			Finally
				Stack.NameValue.Restore;
			End;
		Except
			On E: Exception Do
				Raise EInterpreterException.Create(Row, Col, Source, E);
		End;
	End
	Else
		RaiseError('You cannot use that command here.');
End;

// TPutCommand

Class Function TPutCommand.RegisteredName : String;
Begin
	Result := 'put';
End;

Procedure TPutCommand.Evaluate;
Var
	lCtrl : Integer;
Begin
	// Debug WriteLn('TPutCommand');
	If Stack.Focused.IsCapableOf('TPutCommand') Then
	Begin
		Try
			If Assigned(ExpressionList) Then
				ExpressionList.Evaluate
			Else
				RaiseError('You must provide values.');
			For lCtrl := 0 To Stack.Top.Count - 1 Do
				Stack.Out(Stack.Top.Peek(lCtrl));
		Except
			On E: Exception Do
				Raise EInterpreterException.Create(Row, Col, Source, E);
		End;
	End
	Else
		RaiseError('You cannot use that command here.');
End;

// TExecuteCommand

Class Function TExecuteCommand.RegisteredName : String;
Begin
	Result := 'execute';
End;

Procedure TExecuteCommand.Evaluate;
Var
	lCommandLoader : TCommandParser;
Begin
	// Debug WriteLn('TExecuteCommand');
	If Stack.Focused.IsCapableOf('TExecuteCommand') Then
	Begin
		Try
			Stack.Enter(0);
			Stack.NameValue.Save;
			If Assigned(Properties) Then
				Properties.Evaluate;
			lCommandLoader := TCommandParser.Create(Nil);
			lCommandLoader.PropagateStack(Stack);
			If Stack.Focused.HasTextChild Then
				lCommandLoader.DoLoadCommand(Stack.Focused.IndexedName, Stack.Focused.GetTextChild)
			Else
				RaiseError('Script is empty.');
			lCommandLoader.Evaluate;
		Finally
			Stack.NameValue.Restore;
			Stack.Leave(0);
			FreeAndNil(lCommandLoader);
		End;
	End
	Else
		RaiseError('You cannot use that command here. ' + Stack.Focused.IndexedName);
End;

// TEditCommand

Class Function TEditCommand.RegisteredName : String;
Begin
	Result := 'edit';
End;

Procedure TEditCommand.Evaluate;
Var
	lName : String;
	lFile : TStringList;
Begin
	// Debug WriteLn('TEditCommand');
	If Stack.Focused.IsCapableOf('TEditCommand') Then
	Begin
		Try
			lFile := TStringList.Create;
			If Stack.Focused.HasTextChild Then
				lFile.Text := Stack.Focused.GetTextChild
			Else
				lFile.Text := '';
			lName := 'temporary-' + StringReplace(FloatToStr(Now) + '.tmp', ',', '-', [ rfReplaceAll ]);
			lFile.SaveToFile(lName);
			Stack.ShouldEdit := True;
			Stack.EditFile := lName;
			Stack.Editor := Stack.NameValue.Peek(0).GetValue('EDITOR');
			Stack.ResultEditCommand := '/' + Stack.Focused.NumericalName + ' __loadfromtextfileanddelete__ "' + lName + '"';
		Finally
			FreeAndNil(lFile);
		End;
	End
	Else
		RaiseError('You cannot use that command here.');
End;

// TExportCommand

Class Function TExportCommand.RegisteredName : String;
Begin
	Result := 'export';
End;

Procedure TExportCommand.Evaluate;
Begin
	// Debug WriteLn('TExportCommand');
	If Stack.Focused.IsCapableOf('TExportCommand') Then
		Stack.Namevalue.Peek(0).SetValue(PropertyName, Stack.Namevalue.Top.GetValue(PropertyName))
	Else
		RaiseError('You cannot use that command here.');	
End;

// TCommitCommand

Class Function TCommitCommand.RegisteredName : String;
Begin
	Result := 'commit';
End;

Procedure TCommitCommand.Evaluate;
Var
	lSave : TXMLNode;
	lTemplateEntry : TTemplateEntryNode;
	lTemplateEffect : TTemplateEffectsNode;
Begin
	If Stack.Focused.IsCapableOf('TCommitCommand') Then
	Begin
		Try
			Try
				lSave := Stack.Focused;
				Stack.Focused := Stack.Focused.FindRoot.Find('templates') As TXMLNode;
				Stack.Focused.First;
				While Not Stack.Focused.IsAfterLast Do
				Begin
					lTemplateEntry := Stack.Focused.GetCurrent As TTemplateEntryNode;
					DoParseTemplate(Stack, lTemplateEntry.Source, lTemplateEntry.Destination);
					lTemplateEntry.LastHash := lTemplateEntry.CurrentHash;
					lTemplateEntry.CurrentHash := MD5Print(MD5File(lTemplateEntry.Destination));
					Stack.Focused.Next;
				End;
				Stack.Focused.First;
				While Not Stack.Focused.IsAfterLast Do
				Begin
					lTemplateEntry := Stack.Focused.GetCurrent As TTemplateEntryNode;
					If lTemplateEntry.LastHash <> lTemplateEntry.CurrentHash Then
					Begin
						lTemplateEntry.First;
						While Not lTemplateEntry.IsAfterLast Do
						Begin
							lTemplateEffect := lTemplateEntry.GetCurrent As TTemplateEffectsNode;
							DoRestartService(Stack, lTemplateEffect.Service);
							lTemplateEntry.Next;
						End;
					End;
					Stack.Focused.Next;
				End;
			Finally
				Stack.Focused := lSave;
			End;
		Except
			On E: Exception Do
				Raise EInterpreterException.Create(Row, Col, Source, E);
		End;
	End
	Else
		RaiseError('You cannot use that command here.');	
End;

// TUpdateCommand

Class Function TUpdateCommand.RegisteredName : String;
Begin
	Result := 'update';
End;

Procedure TUpdateCommand.Evaluate;
Begin
	// Debug WriteLn('TUpdateCommand');
	If Stack.Focused.IsCapableOf('TUpdateCommand') Then
		Stack.Focused.FindRoot.Propagate('update', Nil)
	Else
		RaiseError('You cannot use that command here.');	
End;

// TLoadFromTextFile

Class Function TLoadFromTextFile.RegisteredName : String;
Begin
	Result := '__loadfromtextfileanddelete__';
End;

Procedure TLoadFromTextFile.Evaluate;
Var
	lBuffer : TStringList;
	lFile : String;
Begin
	// Debug WriteLn('TLoadFromTextFile');
	If Stack.Focused.IsCapableOf('TLoadFromTextFile') Then
	Begin
		Try
			lBuffer := TStringList.Create;
			Stack.Enter(0);
			If Assigned(Expression) Then
				Expression.Evaluate
			Else
				RaiseError('You must provide a file name.');
			lFile := Stack.Top.Pop;
			lBuffer.LoadFromFile(lFile);
			Sleep(100);
			DeleteFile(lFile);
			Stack.Focused.SetTextChild(lBuffer.Text);
		Finally
			Stack.Leave(0);
			FreeAndNil(lBuffer);
		End;
	End
	Else
		RaiseError('You cannot use that command here.');
End;

End.
